package mylab.starters.mybatis.support.mixedSource;

import cn.hutool.core.text.CharSequenceUtil;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import mylab.base.exceptions.AppRuntimeException;
import mylab.starters.mybatis.exceptions.TenantDataSourceNotFoundException;

import javax.sql.DataSource;
import java.util.Map;
import java.util.function.Function;

/**
 * 多数据源管理服务
 */
public interface TenantDataSourceService {


    /**
     * 按约定规则，获取租户的独立数据源名称
     *
     * @param tenantId 租户ID
     * @return 数据源名称
     */
    static String getTenantDsName(String tenantId) {
        return CharSequenceUtil.format("tenant_{}", tenantId);
    }


    /**
     * 切换到租户的独立数据源，
     *
     * @param tenantId 租户ID
     * @param function 在切换数据源后，执行调用方的业务函数，入参是数据源名称
     * @param <R>      业务函数返回值的类型
     * @return 业务函数返回值，即function的执行结果
     * @throws TenantDataSourceNotFoundException 租户数据源未加载
     */
    default <R> R holdDataSourceByTenant(String tenantId, Function<String, R> function) throws TenantDataSourceNotFoundException {
        String dsName = getTenantDsName(tenantId);
        if (!contains(dsName)) {
            throw new TenantDataSourceNotFoundException(CharSequenceUtil.format("租户ID={} 的数据源={} 未加载", tenantId, dsName));
        }

        return holdDataSource(dsName, function);
    }


    default <R> R holdDataSource(String dsName, Function<String, R> function) {
        if (!contains(dsName)) {
            throw new AppRuntimeException("未知数据源：" + dsName);
        }
        try {
            DynamicDataSourceContextHolder.push(dsName);
            return function.apply(dsName);
        } finally {
            DynamicDataSourceContextHolder.poll();
        }
    }

    /**
     * 是否存在指定数据源
     *
     * @param dsName 数据源名称
     * @return 存在标记
     */
    default boolean contains(String dsName) {
        return getAll().containsKey(dsName);
    }

    /**
     * 获取当前数据源名称
     *
     * @return 当前数据源名称
     */
    default String getCurrentDsName() {
        return DynamicDataSourceContextHolder.peek();
    }

    /**
     * 获取当前数据源
     *
     * @return 当前数据源
     */
    default DataSource getCurrentDs() {
        return getDs(getCurrentDsName());
    }


    /**
     * 获取数据源
     *
     * @param key 数据源名称
     * @return 数据源
     */
    default DataSource getDs(String key) {
        return getAll().get(key);
    }

    /**
     * 动态添加数据源
     *
     * @param property 数据源属性简易描述
     */
    default void addDs(String dsName, DataSourceProperty property) {
        DataSource dataSource = getDsCreator().createDataSource(property);
        getDynamicRoutingDs().addDataSource(dsName, dataSource);
    }

    /**
     * 动态移除数据源
     *
     * @param key
     */
    default void removeDs(String key) {
        getDynamicRoutingDs().removeDataSource(key);
    }

    /**
     * 获取所有数据源
     * key   -- 数据源名称
     * value -- 数据源
     *
     * @return 所有数据源
     */
    default Map<String, DataSource> getAll() {
        return getDynamicRoutingDs().getDataSources();
    }


    /**
     * 获取动态路由数据源
     *
     * @return 动态路由数据源
     */
    DynamicRoutingDataSource getDynamicRoutingDs();

    /**
     * 获取默认的数据源创建器
     *
     * @return 数据源创建器
     */
    DefaultDataSourceCreator getDsCreator();
}
